package org.apache.rmiloader.kernel;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.apache.log4j.Logger;
import org.apache.rmiloader.action.RmiReport;

/**
 * Look inside jar file and introspect each class to find RMI Class
 * Provide a list of RMI Classes found
 * @author Joris
 *
 */
public class RmiLookUp {

	//all classes
	private Set<String> classes = new HashSet<String>();
	
	private final String RMI_INTERFACE = "java.rmi.Remote";
	
	private Set<Class<?>> itRmi = new HashSet<Class<?>>();
	private Set<Class<?>> implRmi = new HashSet<Class<?>>();
	private HashMap<Class<?>, Class<?>> child2parent = new HashMap<Class<?>, Class<?>>();
	
	private RmiReport report = RmiReport.getInstance();
	
	private Logger logger = Logger.getLogger(RmiLookUp.class);
	private ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
	
	public RmiLookUp() {
		initLoadClasses();
		figureOutRmiInterface();
		figureOutRmiImpl();
	}
	
	private void initLoadClasses() {
		// get classes into all jar files
		JarFinder jars = new JarFinder();
		for (String jar : jars.getJarFiles()) {
			try {
				InputStream is = new FileInputStream(jar);
				JarInputStream jis = new JarInputStream(is) ;
				JarEntry entry = jis.getNextJarEntry() ;
				while (entry != null) {
					if (!entry.isDirectory() && entry.getName().matches(".*\\.class")) {
						classes.add(entry.getName());
					}
					entry = jis.getNextJarEntry() ;
				}
			}
			catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		
		// Print log INFO
		logger.info(classes.size() + " classes loaded");
	}
	
	private void figureOutRmiInterface() {
		// get rmi interface 
		for (String sclass : classes) {
			try {
				sclass = sclass.replaceFirst("\\.class", "").replaceAll("/", ".");
				Class<?> service = contextLoader.loadClass(sclass);
				for (Class<?> it : service.getInterfaces()) {
					if (it.getName().matches(RMI_INTERFACE))
						itRmi.add(service);
				}
				} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
		report.setClassImpl(itRmi);
		
		// Print log INFO
		logger.info(itRmi.size() + " interfaces <" + RMI_INTERFACE + "> found");
	}		
	
	private void figureOutRmiImpl() {
		//TODO: Improve lookup algo (By hash method research for example)
		// get rmi implementation
		for (String sclass : classes) {
			try {
				sclass = sclass.replaceFirst("\\.class", "").replaceAll("/", ".");
				Class<?> service = contextLoader.loadClass(sclass);
				for (Class<?> impl : service.getInterfaces()) {
					for (Class<?> it : itRmi) {
						if (impl.getName().matches(it.getName())) {
							implRmi.add(service);
							child2parent.put(service, it);
							break ;
						}
					}
				}
			} catch (ClassNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
		report.setClassImpl(implRmi);
		report.setChild2Parent(child2parent);
		
		// Print log INFO
		logger.info(implRmi.size() + " <" + RMI_INTERFACE + "> implementation found");
		for (Class<?> impl : implRmi) {
			logger.info("<" + RMI_INTERFACE + "> implementation - " + impl.getName());
		}
	}
	
	public Set<Class<?>> getRmiServices() {
		return implRmi;
	}
}
